home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue40 / Threads / MultiThreadedMainLoop.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1998-09-08  |  6.7 KB  |  211 lines

  1. unit MultiThreadedMainLoop;
  2.  
  3. interface
  4.  
  5. uses
  6.   Windows,
  7.   SysUtils,
  8.   Classes,
  9.   Forms,
  10.   HVClass,
  11.   HVSyncObjs,
  12.   ExtCtrls
  13.   ;
  14.  
  15. type
  16.   TIdleTimerEvent = procedure (Sender: TObject; var Done: Boolean; AppIsIdle: boolean) of object;
  17.   TMultiThreadedMainLoop = class(TObject)
  18.   private
  19.     FHasBeenIdle : boolean;
  20.     FIdleTimer   : TTimer;
  21.     FIdleHookList: TEventList;
  22.     FOldAppIdle  : TIdleEvent;
  23.     FIdleWaitTime: integer;
  24.     FBusyWaitTime: integer;
  25.     FThreadEventList: TThreadEventList;
  26.     procedure SetIdleTimerInterval(Value: integer);
  27.     function GetIdleTimerInterval: integer;
  28.   protected
  29.     procedure CallIdleHooks(Sender: TObject; var HooksDone: boolean; AppIsIdle: boolean);
  30.     procedure OnIdleTimer(Sender: TObject);
  31.     procedure AppIdle(Sender: TObject; var Done: boolean);
  32.   public
  33.     constructor Create;
  34.     destructor Destroy; override;
  35.     // Idle hook support
  36.     procedure AddIdleHook(Notify: TIdleTimerEvent);
  37.     procedure RemoveIdleHook(Notify: TIdleTimerEvent);
  38.  
  39.     procedure TriggerOnHandle(aHandle: THandle; anOnTrigger: TNotifyEvent);
  40.     procedure TriggerOnObject(aHandleObject: THandleObject; anOnTrigger: TNotifyEvent);
  41.     procedure TriggerOnThread(aThread: TThread; anOnTrigger: TNotifyEvent);
  42.  
  43.     property IdleWaitTime: integer read FIdleWaitTime write FIdleWaitTime;
  44.     property BusyWaitTime: integer read FBusyWaitTime write FBusyWaitTime;
  45.     property ThreadEventList: TThreadEventList read FThreadEventList write FThreadEventList;
  46.     property IdleTimerInterval: integer read GetIdleTimerInterval write SetIdleTimerInterval;
  47.   end;
  48.  
  49. var
  50.   MultiThreadedMainLoop: TMultiThreadedMainLoop = nil;
  51.  
  52. implementation
  53.  
  54. uses
  55.   HVUtils;
  56.  
  57. { TMultiThreadedMainLoop }
  58.  
  59. constructor TMultiThreadedMainLoop.Create;
  60. begin
  61.   inherited Create;
  62.   // By default, just block until we have a message or one of the handles signals
  63.   FIdleWaitTime := INFINITE;
  64.   FBusyWaitTime := 0;
  65.  
  66.   // Create the list used for the thread events
  67.   FThreadEventList := TThreadEventList.Create;
  68.  
  69.   // Save and setup OnIdle handler, lets hope newcomers do the same...
  70.   FOldAppIdle := Application.OnIdle;
  71.   Application.OnIdle := AppIdle;
  72.  
  73.   FIdleTimer := TTimer.Create(nil);
  74.   FIdleTimer.Interval := 100;  // every 100 ms, about 10 times pr second
  75.   FIdleTimer.OnTimer := OnIdleTimer;
  76.   FIdleTimer.Enabled := true;
  77. end;
  78.  
  79. destructor TMultiThreadedMainLoop.Destroy;
  80. begin
  81.   // Stop being nagged by the timer
  82.   FreeObject(FIdleTimer);
  83.   // Restore the old OnIdle handler
  84.   Application.OnIdle := FOldAppIdle;
  85.   FreeObject(FIdleHookList);
  86.   FreeObject(FThreadEventList);
  87.   inherited Destroy;
  88. end;
  89.  
  90. function TMultiThreadedMainLoop.GetIdleTimerInterval: integer;
  91. begin
  92.   Result := FIdleTimer.Interval;
  93. end;
  94.  
  95. procedure TMultiThreadedMainLoop.SetIdleTimerInterval(Value: integer);
  96. begin
  97.   FIdleTimer.Interval := Value;
  98. end;
  99.  
  100. procedure TMultiThreadedMainLoop.TriggerOnHandle(aHandle: THandle; anOnTrigger: TNotifyEvent);
  101. begin
  102.   FThreadEventList.TriggerOnHandle(aHandle, anOnTrigger);
  103. end;
  104.  
  105. procedure TMultiThreadedMainLoop.TriggerOnObject(aHandleObject: THandleObject; anOnTrigger: TNotifyEvent);
  106. begin
  107.   FThreadEventList.TriggerOnObject(aHandleObject, anOnTrigger);
  108. end;
  109.  
  110. procedure TMultiThreadedMainLoop.TriggerOnThread(aThread: TThread; anOnTrigger: TNotifyEvent);
  111. begin
  112.   FThreadEventList.TriggerOnThread(aThread, anOnTrigger);
  113. end;
  114.  
  115. procedure TMultiThreadedMainLoop.AddIdleHook(Notify: TIdleTimerEvent);
  116. begin
  117.   TEventList.AddEventListMember(FIdleHookList, TEventProc(Notify));
  118. end;
  119.  
  120. procedure TMultiThreadedMainLoop.RemoveIdleHook(Notify: TIdleTimerEvent);
  121. begin
  122.   TEventList.RemoveEventListMember(FIdleHookList, TEventProc(Notify));
  123. end;
  124.  
  125. procedure TMultiThreadedMainLoop.CallIdleHooks(Sender: TObject; var HooksDone: boolean; AppIsIdle: boolean);
  126. var
  127.   i      : integer;
  128.   Done   : boolean;
  129.   Method : TMethod;
  130. begin
  131.   // Call the old handler and check if it is done
  132.   HooksDone := true;
  133.   if Assigned(FOldAppIdle) and AppIsIdle then
  134.     FOldAppIdle(Sender, HooksDone);
  135.  
  136.   // Then call any idle subscribers and check if they are done
  137.   if Assigned(FIdleHookList) then
  138.   begin
  139.     for i := 0 to FIdleHookList.Count-1 do
  140.     begin
  141.       Done := true;
  142.       Method := FIdleHookList.Items[i];
  143.       TIdleTimerEvent(Method)(Sender, Done, AppIsIdle); // Dividing up the code below like this is ok!
  144. //  TIdleTimerEvent(FIdleHookList.Items[i])(Sender, Done);  // Compiler bug: Generates wrong code for this!! (compiles fine, though
  145.       HooksDone := HooksDone and Done;
  146.     end;
  147.   end;
  148. end;
  149.  
  150. procedure TMultiThreadedMainLoop.AppIdle(Sender: TObject; var Done: boolean);
  151. // Whenever the application becomes idle, i.e. there are no messages in the
  152. // message queue, this procedure is entered.
  153. var
  154.   IdleChildrenDone: boolean;
  155.   WaitTime        : integer;
  156.   WaitResult      : TWaitResult;
  157. begin
  158.   repeat
  159.     // Tell the timer-loop that we have actully been idle
  160.     FHasBeenIdle := true;
  161.  
  162.     // Now call all other idle hooks, indicating that the application is actually idle
  163.     CallIdleHooks(Sender, IdleChildrenDone, true);
  164.  
  165.     // If there are one or more idle handler that is not done yet, don't pause very long before returning
  166.     if IdleChildrenDone
  167.     then WaitTime := IdleWaitTime
  168.     else WaitTime := BusyWaitTime;
  169.  
  170.     // Wait for a message or a timeout - handles all signaled objects in the process
  171.     WaitResult := FThreadEventList.WaitUntil(WaitTime, [wrMessage, wrTimeOut]);
  172.  
  173.     // Loop until we get a message
  174.   until WaitResult = wrMessage;
  175.  
  176.   // Always return Done=false to signal that the message loop should go back here when it has read all messages
  177.   Done := false;
  178. end;
  179.  
  180. {procedure TMultiThreadedMainLoop.WaitUntil(Condition: TConditionEvent);
  181. var
  182.   WaitResult: TWaitResult;
  183. begin
  184.   repeat
  185.     // Wait for a message or a timeout - handles all signaled objects in the process
  186.     WaitResult := FThreadEventList.WaitUntil(IdleWaitTime, [wrSignaled, wrError]);
  187.     // Loop until we are satisfied
  188.   until (WaitResult = wrError) or (Condition = true);
  189. end;}
  190.  
  191. procedure TMultiThreadedMainLoop.OnIdleTimer(Sender: TObject);
  192. // This timer handler is called (roughly) 10 times pr second
  193. // This is to allow signaled objects to be handled even when other message loops than TApplication is running (e.g. menu)
  194. var
  195.   IdleChildrenDone: boolean;
  196. begin
  197.   // Some time since we were idle?
  198.   if not FHasBeenIdle then
  199.   begin
  200.     // Now call all idle hooks, indicating that the application is _not_ actually idle
  201.     CallIdleHooks(Sender, IdleChildrenDone, false);
  202.     // Empty the list of signaled objects
  203.     while (ThreadEventList.WaitOneAndTrigger(0) = wrSignaled) do
  204.       {Loop};
  205.   end;
  206.   // Reset the idle flag
  207.   FHasBeenIdle := false;
  208. end;
  209.  
  210. end.
  211.